﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.Diagnostics.Contracts;
using System.Reflection;
using System.Threading;

using System.Xml.Linq;
using System.Net;

using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography;
using System.Security;
using System.IO;
using System.Collections;
using System.Collections.Concurrent;

using King.Reflection;
using King.Declarative;
using King.Extensions;
using King.Text;
using King.Text.Xml;
using King.Collections;
using System.Globalization;

namespace King.Rest {

    internal interface IRestValueInfo {
        IFormatProvider Provider { get; }
        string ValueOrFormat { get; }
    }

    internal sealed class RestDefaultFormatter : ICustomFormatter, IFormatProvider {
        internal static readonly RestDefaultFormatter Formatter = new RestDefaultFormatter();

        private RestDefaultFormatter() { }
        public string Format(string format, object value, IFormatProvider formatProvider) {
            if (value is IFormattable)
                return ((IFormattable)value).ToString(format, CultureInfo.CurrentCulture);

            else if (value != null)
                return value.ToString();

            else
                return String.Empty;
        }
        public object GetFormat(Type formatType) {
            // boilerplate (see: http://msdn.microsoft.com/en-us/library/system.icustomformatter.aspx)
            if (formatType == typeof(ICustomFormatter))
                return this;
            else
                return null;
        }
    }
    internal sealed class RestFormatter : ICustomFormatter {

        private ICustomFormatter m_formatter;

        public RestFormatter(ICustomFormatter formatter) {
            m_formatter = formatter;
        }
        
        public string Format(string format, object value, IFormatProvider formatProvider) {
            var restProvider = (RestProvider)formatProvider;
            var formattedValue = m_formatter.Format(format, value, restProvider.Provider);

            // escape the formatted value 
            var escapedFormattedValue = Uri.EscapeDataString(formattedValue);

            return escapedFormattedValue;
        }
    }
    internal sealed class RestProvider : IFormatProvider {

        private IFormatProvider m_provider;

        public RestProvider(IFormatProvider provider) {
            m_provider = provider;
        }

        public object GetFormat(Type formatType) {
            var formatter = m_provider.GetFormat(formatType) as ICustomFormatter;
            if (formatter == null)
                return null;

            return new RestFormatter(formatter);
        }

        internal IFormatProvider Provider {
            get { return m_provider; }
        }
    }

    internal struct RestVariable {
        public static implicit operator string(RestVariable value) {
            return value.Value;
        }

        private IFormatProvider m_provider;
        private string m_format;
        private object m_nameArgumentOrValue;
        private object m_target;
        private string m_result;

        internal RestVariable(RestMemberInfo member, string nameOrFormat)
            : this(member.FormatProvider, nameOrFormat, member.Name) {
            // loadtime variables
        }
        internal RestVariable(IRestValueInfo valueInfo, object context, object argumentOrValue)
            : this(valueInfo.Provider, valueInfo.ValueOrFormat, argumentOrValue, context) {
            // runtime variables
        }
        private RestVariable(IFormatProvider provider, string format, object nameArgumentOrValue, object target = null) {
            m_provider = provider;
            m_format = format;
            m_nameArgumentOrValue = nameArgumentOrValue;
            m_target = target;
            m_result = null;
        }

        internal string Value {
            get {
                if (m_result == null) {
                    if (m_format == null)
                        // never escape constant values (common case is they are coded constents)
                        // if escaping is needed use a simple format string {0}
                        m_result = m_nameArgumentOrValue.ToString();
                    
                    else {
                        var provider = new VariableFormatter(
                            // escape any variables formatted by the user provider
                            new Formatter(m_provider, Uri.EscapeDataString),

                            // optionally escape named variables (<%:escapeMe%> vs <%=literal%>)
                            Uri.EscapeDataString
                        );

                        m_result = string.Format(provider, m_format, m_nameArgumentOrValue, m_target);
                    }
                }

                return m_result;
            }
        }
        internal object Object {
            get { return m_nameArgumentOrValue; }
        }

        public override string ToString() {
            return Value;
        }
    }

    public abstract class RestAttribute : Attribute {
        internal const AttributeTargets ClassFieldPropertyMethodParameter =
            AttributeTargets.Class |
            AttributeTargets.Field |
            AttributeTargets.Property |
            AttributeTargets.Method |
            AttributeTargets.Parameter;

        internal const AttributeTargets FieldProperty =
            AttributeTargets.Field |
            AttributeTargets.Property;

        internal const AttributeTargets ClassMethod =
            AttributeTargets.Class |
            AttributeTargets.Method;

        internal RestAttribute() { }
    }    

    public enum RestMethod {
        Get,
        Put,
        Post,
        Delete,
        Head,
        Merge,
    }

    [AttributeUsage(AttributeTargets.Method)]
    public abstract class RestRequestAttribute : RestAttribute {
        private RestMethod m_method;
        public RestRequestAttribute(RestMethod method) {
            m_method = method;
        }
        public RestMethod Method {
            get { return m_method; }
        }
    }
    public sealed class RestGetAttribute : RestRequestAttribute {
        public RestGetAttribute() : base(RestMethod.Get) { }
    }
    public sealed class RestPostAttribute : RestRequestAttribute {
        public RestPostAttribute() : base(RestMethod.Post) { }
    }
    public sealed class RestPutAttribute : RestRequestAttribute {
        public RestPutAttribute() : base(RestMethod.Put) { }
    }
    public sealed class RestDeleteAttribute : RestRequestAttribute {
        public RestDeleteAttribute() : base(RestMethod.Delete) { }
    }
    public sealed class RestHeadAttribute : RestRequestAttribute {
        public RestHeadAttribute() : base(RestMethod.Head) { }
    }
    public sealed class RestMergeAttribute : RestRequestAttribute {
        public RestMergeAttribute() : base(RestMethod.Merge) { }
    }

    public abstract class RestPartAttribute : RestAttribute {
    }

    [AttributeUsage(ClassFieldPropertyMethodParameter)]
    public abstract class RestMetaPartAttribute : RestPartAttribute {
    }
    public sealed class RestFormatterAttribute : RestMetaPartAttribute {
        private Type m_type;
        public RestFormatterAttribute(Type type) {
            m_type = type;
        }
        public Type Type {
            get { return m_type; }
        }
    }
    [AttributeUsage(ClassMethod)]
    public sealed class RestTemplateAttribute : RestMetaPartAttribute {
        private Type m_type;
        public RestTemplateAttribute(Type type) {
            m_type = type;
        }
        public Type Type {
            get { return m_type; }
        }
    }

    [AttributeUsage(ClassFieldPropertyMethodParameter)]
    public abstract class RestHttpPartAttribute : RestPartAttribute {
    }
    public sealed class RestProtocolAttribute : RestHttpPartAttribute {
        public bool Https { get; set; }
    }
    public sealed class RestHostAttribute : RestHttpPartAttribute {
        private string m_valueOrFormat;
        public RestHostAttribute() { }
        public RestHostAttribute(string valueOrFormat) {
            m_valueOrFormat = valueOrFormat;
        }
        public string ValueOrFormat {
            get { return m_valueOrFormat; }
        }
    }
    public sealed class RestPathAttribute : RestHttpPartAttribute {
        private string m_valueOrFormat;
        public RestPathAttribute() { }
        public RestPathAttribute(string valueOrFormat) {
            m_valueOrFormat = valueOrFormat;
        }
        public string ValueOrFormat {
            get { return m_valueOrFormat; }
        }
        public bool Append { get; set; }
    }

    [AttributeUsage(ClassFieldPropertyMethodParameter, AllowMultiple = true)]
    public sealed class RestQueryAttribute : RestHttpPartAttribute {
        private string m_valueOrFormat;
        private string m_nameOrFormat;
        public RestQueryAttribute() {
        }
        public RestQueryAttribute(string nameOrFormat)
            : this(nameOrFormat, null) {
        }
        public RestQueryAttribute(string nameOrFormat, string valueOrFormat) {
            m_nameOrFormat = nameOrFormat;
            m_valueOrFormat = valueOrFormat;
        }
        public string NameOrFormat {
            get { return m_nameOrFormat; }
        }
        public string ValueOrFormat {
            get { return m_valueOrFormat; }
        }
    }

    [AttributeUsage(ClassFieldPropertyMethodParameter, AllowMultiple = true)]
    public sealed class RestHeaderAttribute : RestHttpPartAttribute {
        private string m_valueOrFormat;
        private string m_nameOrFormat;
        public RestHeaderAttribute() {
        }
        public RestHeaderAttribute(string nameOrFormat)
            : this(nameOrFormat, null) {
        }
        public RestHeaderAttribute(string nameOrFormat, string valueOrFormat) {
            m_valueOrFormat = valueOrFormat;
            m_nameOrFormat = nameOrFormat;
        }
        public string NameOrFormat {
            get { return m_nameOrFormat; }
        }
        public string ValueOrFormat {
            get { return m_valueOrFormat; }
        }
    }

    [AttributeUsage(ClassFieldPropertyMethodParameter, AllowMultiple = true)]
    public sealed class RestContentAttribute : RestHttpPartAttribute {
        private string m_valueOrFormat;
        private string m_nameOrFormat;
        public RestContentAttribute() {
        }
        public RestContentAttribute(string nameOrFormat)
            : this(nameOrFormat, null) {
        }
        public RestContentAttribute(string nameOrFormat, string valueOrFormat) {
            m_valueOrFormat = valueOrFormat;
            m_nameOrFormat = nameOrFormat;
        }
        public string NameOrFormat {
            get { return m_nameOrFormat; }
        }
        public string ValueOrFormat {
            get { return m_valueOrFormat; }
        }
    }

    [AttributeUsage(FieldProperty)]
    public sealed class RestCertificateAttribute : RestHttpPartAttribute {
    }

    [AttributeUsage(FieldProperty)]
    public sealed class RestParentAttribute : RestHttpPartAttribute {
    }

    // loader
    internal static class RestLoader {
        private static ConcurrentDictionary<object, object> s_wrappers;

        static RestLoader() {
            s_wrappers = new ConcurrentDictionary<object, object>();
        }

        internal static T Get<T>(object info) {
            if (info == null)
                return default(T);

            var result = s_wrappers.GetValueOrDefault(info);
            if (result != null)
                return (T)result;

            return (T)s_wrappers.GetOrAdd(info, typeof(T).Activate(info));
        }
        internal static RestRequestInfo GetRequest(MethodInfo method) {
            if (!method.IsDefined<RestRequestAttribute>())
                throw new InvalidOperationException("RestRequestAttribute not found.");

            return Get<RestRequestInfo>(method);
        }
        internal static RestTypeInfo GetType(Type type) {
            return Get<RestTypeInfo>(type);
        }
    }

    // each reflection Member/ParameterInfo maps to a subclass of RestMemberInfo
    internal abstract class RestMemberInfo {
        private readonly ICustomAttributeProvider m_attributeProvider;
        private readonly ICustomAttributeProvider m_parentAttributeProvider;
        private IEnumerable<RestMetaPartInfo> m_declaredMetaParts;
        private IEnumerable<RestHttpPartInfo> m_declaredHttpParts;

        internal RestMemberInfo(
            ICustomAttributeProvider attributeProvider,
            ICustomAttributeProvider parentAttributeProvider) {
            m_attributeProvider = attributeProvider;
            m_parentAttributeProvider = parentAttributeProvider;
        }

        private RestMemberInfo ParentAttributeProvider {
            get {
                // parent is a method
                if (m_parentAttributeProvider is MethodInfo) 
                    return RestLoader.Get<RestRequestInfo>(m_parentAttributeProvider); 
                
                // parent is a declared type
                return RestLoader.Get<RestTypeInfo>(m_parentAttributeProvider);
            }
        }
        private IEnumerable<RestMetaPartInfo> DeclaredMetaParts() {
            if (m_declaredMetaParts == null) {
                var assembly = Assembly.GetExecutingAssembly();

                var metaPartAttributes = AttributeProvider.GetCustomAttributes(typeof(RestMetaPartAttribute), false);
                var metaParts = RestPartInfo.ConstructParts(this, metaPartAttributes).Cast<RestMetaPartInfo>().ToArray();
                Interlocked.CompareExchange(ref m_declaredMetaParts, metaParts, null);
            }
            return m_declaredMetaParts;
        }

        internal ICustomAttributeProvider TargetParent {
            get { return m_parentAttributeProvider; }
        }
        internal virtual IEnumerable<RestHttpPartInfo> DeclaredHttpParts() {
            if (m_declaredHttpParts == null) {
                var assembly = Assembly.GetExecutingAssembly();

                var httpPartAttributes = AttributeProvider.GetCustomAttributes(typeof(RestHttpPartAttribute), false);
                var httpParts = RestPartInfo.ConstructParts(this, httpPartAttributes).Cast<RestHttpPartInfo>().ToArray();
                Interlocked.CompareExchange(ref m_declaredHttpParts, httpParts, null);
            }
            return m_declaredHttpParts;
        }

        internal IEnumerable<RestMetaPartInfo> MetaParts() {
            var metaParts = DeclaredMetaParts();
            
            var parent = ParentAttributeProvider;
            if (parent != null)
                metaParts = metaParts.Concat(ParentAttributeProvider.MetaParts());

            return metaParts;
        }
        internal IFormatProvider FormatProvider {
            get {
                var formatProvider = MetaParts().OfType<RestFormatterInfo>().FirstOrDefault();
                if (formatProvider == null)
                    return null;
                return formatProvider.Provider;
            }
        }
        internal RestTemplateInfo Template {
            get { return MetaParts().OfType<RestTemplateInfo>().FirstOrDefault(); }
        }
        internal string Name {
            get {
                return AttributeProvider is ParameterInfo ?
                    ((ParameterInfo)AttributeProvider).Name :
                    ((MemberInfo)AttributeProvider).Name;
            }
        }

        public ICustomAttributeProvider AttributeProvider {
            get { return m_attributeProvider; }
        }

        public override string ToString() {
            return Name;
        }
    }
    internal sealed class RestRequestInfo : RestMemberInfo {
        private readonly Type m_type;
        private readonly string m_httpMethod;
        private readonly IEnumerable<RestParameterInfo> m_parameters;

        internal RestRequestInfo(MethodInfo method)
            : base(method, method.DeclaringType) {

            m_parameters = method.GetParameters()
                .Select(o => RestLoader.Get<RestParameterInfo>(o))
                .ToArray();

            var attribute = method.GetAttribute<RestRequestAttribute>();
            m_httpMethod = attribute.Method.ToString().ToUpperInvariant();
            m_type = method.DeclaringType;
        }

        private RestTypeInfo Type {
            get { return RestLoader.Get<RestTypeInfo>(m_type); }
        }

        internal string HttpMethod {
            get { return m_httpMethod; }
        }
        internal IEnumerable<RestHttpPartInfo> ContextHttpParts() {
            var result = DeclaredHttpParts();
            if (Type != null)
                result = result.Concat(Type.HttpParts());
            return result;
        }
        internal IEnumerable<RestHttpPartInfo> ParameterHttpParts() {
            return m_parameters.Select(o => o.HttpPart);
        }
    }
    internal sealed class RestParameterInfo : RestMemberInfo {
        private RestNakedParameterInfo m_nakedParameterInfo;
        private readonly ParameterInfo m_parameter;

        internal RestParameterInfo(ParameterInfo parameter)
            : base(parameter, parameter.Member) {
        }

        internal override IEnumerable<RestHttpPartInfo> DeclaredHttpParts() {
            var parts = base.DeclaredHttpParts();
            if (parts.Any())
                return parts;

            if (m_nakedParameterInfo == null)
                Interlocked.CompareExchange(ref m_nakedParameterInfo, new RestNakedParameterInfo(this), null);

            return new[] { m_nakedParameterInfo };
        }

        internal RestHttpPartInfo HttpPart {
            get {
                if (DeclaredHttpParts().MoreThanOne())
                    throw new ArgumentException(
                        "Cannot declare more than one RestPart per parameter.");

                return DeclaredHttpParts().Single(); 
            }
        }
    }
    internal sealed class RestPropertyFieldInfo : RestMemberInfo {
        internal RestPropertyFieldInfo(MemberInfo propertyField)
            : base(propertyField, propertyField.DeclaringType) {
        }

    }
    internal sealed class RestTypeInfo : RestMemberInfo {
        private readonly Type m_type;
        private readonly IEnumerable<RestMemberInfo> m_propertyFields;

        internal RestTypeInfo(Type type)
            : base(type, type.BaseType) {

            m_type = type;

            var propertyFields =
                from o in type.Get<MemberInfo>(declaredOnly: true, isPropertyOrField: true)
                where o.IsDefined<RestHttpPartAttribute>()
                select RestLoader.Get<RestPropertyFieldInfo>(o);

            m_propertyFields = propertyFields.ToArray();
        }

        private IEnumerable<RestHttpPartInfo> InheritedHttpParts() {
            if (m_type.BaseType == null)
                return Enumerable.Empty<RestHttpPartInfo>();
            return RestLoader.Get<RestTypeInfo>(m_type.BaseType).HttpParts();
        }

        internal IEnumerable<RestHttpPartInfo> HttpParts() {
            var memberParts = m_propertyFields.SelectMany(o => o.DeclaredHttpParts()).ToArray();

            var parts =
                // property/field parts
                memberParts.ExceptWhere(o => o is RestParentInfo)
                // declared parts
                .Concat(DeclaredHttpParts())
                // inherited parts
                .Concat(InheritedHttpParts())
                // nested scope
                .Concat(memberParts.Where(o => o is RestParentInfo))
                .ToArray();

            return parts;
        }
    }

    // each member has one or more parts
    [DebuggerDisplay("Type = {GetType().Name}, Member = {Member}, Value = {ToString()}")]
    internal abstract class RestPartInfo {
        internal static IEnumerable<RestPartInfo> ConstructParts(RestMemberInfo member, IEnumerable<object> attributes) {
            var assembly = Assembly.GetExecutingAssembly();
            var parts = attributes.Select(o => ConstructPart(assembly, member, o)).ToArray();
            return parts;
        }
        private static RestPartInfo ConstructPart(Assembly assembly, RestMemberInfo member, object attribute) {
            var attributeType = attribute.GetType();
            var partInfoFullname = attribute.GetType().FullName.ReplaceEnd("Attribute", "Info");
            var partInfoType = assembly.GetType(partInfoFullname);
            var partInfo = (RestPartInfo)partInfoType.Activate(member, attribute);
            return partInfo;
        }

        private readonly RestMemberInfo m_member;

        internal RestPartInfo(RestMemberInfo member) {
            m_member = member;
        }

        internal RestMemberInfo Member {
            get { return m_member; }
        }
    }

    // parts known at load time that are needed to load http infos
    internal abstract class RestMetaPartInfo : RestPartInfo {
        internal RestMetaPartInfo(RestMemberInfo member) 
            : base(member) {
        }
    }
    internal sealed class RestFormatterInfo : RestMetaPartInfo {
        private IFormatProvider m_provider;

        internal RestFormatterInfo(RestMemberInfo member, RestFormatterAttribute attribute)
            : base(member) {
            m_provider = (IFormatProvider)attribute.Type.Activate();
        }

        internal IFormatProvider Provider {
            get { return m_provider; }
        }
    }
    internal sealed class RestTemplateInfo : RestMetaPartInfo {
        private Type m_type;

        internal RestTemplateInfo(RestMemberInfo member, RestTemplateAttribute attribute)
            : base(member) {
            m_type = attribute.Type;
        }

        internal Type Type {
            get { return m_type; }
        }
    }

    // http parts that known their parts at load time
    internal abstract class RestHttpPartInfo : RestPartInfo {
        protected RestHttpPartInfo(RestMemberInfo member) 
            : base(member) { 
        }

        public IFormatProvider Provider {
            get { return Member.FormatProvider; }
        }
    }
    internal sealed class RestProtocolInfo : RestHttpPartInfo {
        private bool m_https;

        internal RestProtocolInfo(RestMemberInfo member, RestProtocolAttribute attribute)
            : base(member) {

            m_https = attribute.Https;
        }

        internal bool Https {
            get { return m_https; }
        }
        public override string ToString() {
            return Https ? "https" : "http";
        }
    }
    internal sealed class RestHostInfo : RestHttpPartInfo, IRestValueInfo {
        private string m_valueOrFormat;

        internal RestHostInfo(RestMemberInfo member, RestHostAttribute attribute)
            : base(member) {

            m_valueOrFormat = attribute.ValueOrFormat;
        }

        public string ValueOrFormat {
            get { return m_valueOrFormat; }
        }
        public override string ToString() {
            return ValueOrFormat;
        }
    }
    internal sealed class RestPathInfo : RestHttpPartInfo, IRestValueInfo {
        private string m_valueOrFormat;

        internal RestPathInfo(RestMemberInfo member, RestPathAttribute attribute)
            : base(member) {

            m_valueOrFormat = attribute.ValueOrFormat;
        }

        public string ValueOrFormat {
            get { return m_valueOrFormat; }
        }
        public override string ToString() {
            return ValueOrFormat;
        }
    }
    internal sealed class RestQueryInfo : RestHttpPartInfo, IRestValueInfo {
        private RestVariable m_name;
        private string m_valueOrFormat;

        internal RestQueryInfo(RestMemberInfo member, RestQueryAttribute attribute)
            : base(member) {

            m_valueOrFormat = attribute.ValueOrFormat;
            m_name = new RestVariable(member, attribute.NameOrFormat);
        }

        public string ValueOrFormat {
            get { return m_valueOrFormat; }
        }
        public string Name {
            get { return m_name.Value; }
        }
        public override string ToString() {
            return new TextJoin('=', Name, ValueOrFormat).ReadToEnd();
        }
    }
    internal sealed class RestHeaderInfo : RestHttpPartInfo, IRestValueInfo {
        private RestVariable m_name;
        private string m_valueOrFormat;

        internal RestHeaderInfo(RestMemberInfo member, RestHeaderAttribute attribute)
            : base(member) {

            m_valueOrFormat = attribute.ValueOrFormat;
            m_name = new RestVariable(member, attribute.NameOrFormat);
        }

        public string ValueOrFormat {
            get { return m_valueOrFormat; }
        }
        public string Name {
            get { return m_name.Value; }
        }
        public override string ToString() {
            return new TextJoin(": ", Name, ValueOrFormat).ReadToEnd();
        }
    }
    internal sealed class RestContentInfo : RestHttpPartInfo, IRestValueInfo {
        private RestVariable m_name;
        private string m_valueOrFormat;

        internal RestContentInfo(RestMemberInfo member, RestContentAttribute attribute)
            : base(member) {

            m_valueOrFormat = attribute.ValueOrFormat;
            m_name = new RestVariable(member, attribute.NameOrFormat);
        }

        public string ValueOrFormat {
            get { return m_valueOrFormat; }
        }
        public string Name {
            get { return m_name; }
        }
        public override string ToString() {
            if (ValueOrFormat == null)
                return null;
            return ValueOrFormat.ToString();
        }
    }
    internal sealed class RestCertificateInfo : RestHttpPartInfo {
        internal RestCertificateInfo(RestMemberInfo member, RestCertificateAttribute attribute)
            : base(member) {
        }
    }

    // http parts that generate more parts at runtime
    internal abstract class RestDynamicHttpPartInfo : RestHttpPartInfo {
        internal RestDynamicHttpPartInfo(RestMemberInfo member)
            : base(member) {
        }
    }
    internal sealed class RestParentInfo : RestDynamicHttpPartInfo {
        internal RestParentInfo(RestPropertyFieldInfo fieldProperty, RestParentAttribute attribute)
            : base(fieldProperty) {
        }
    }
    internal sealed class RestNakedParameterInfo : RestDynamicHttpPartInfo {
        internal RestNakedParameterInfo(RestParameterInfo parameter)
            : base(parameter) {
        }
    }

    // runtime
    [DebuggerDisplay("Info = {HttpPartInfo.GetType().Name}, Value = {ToString()}")]
    internal abstract class RestHttpPart {

        internal static IEnumerable<RestHttpPart> Construct(RestHttpPartInfo info, object context, object argument = null) {

            // bail if there is no value
            object value;
            if (!GetValue(info.Member.AttributeProvider, context, argument, out value))
                return Enumerable.Empty<RestHttpPart>();

            // resolve runtime type (RestXXXInfo => RestXXX)
            var runtimeTypeName = info.GetType().FullName.ReplaceEnd("Info");
            var runtimeType = Assembly.GetExecutingAssembly().GetType(runtimeTypeName, true);

            // activate runtime type
            var part = (RestHttpPart)runtimeType.Activate(info, context, value);

            // some parts generate more parts
            var parts = part as IEnumerable<RestHttpPart>;
            if (parts != null)
                return parts;

            return new[] { part };
        }
        internal static bool GetValue(ICustomAttributeProvider info, object context, object argument, out object value) {
            value = null;

            if (argument is bool && (bool)argument == false)
                return false;

            if (info is MethodInfo)
                return true;

            if (info is Type)
                return true;

            if (info is ParameterInfo)
                value = argument; 

            else if (info is PropertyInfo)
                value = ((PropertyInfo)info).GetValue(context); 

            else if (info is FieldInfo)
                value = ((FieldInfo)info).GetValue(context); 

            else
                throw new InvalidOperationException(
                    "Unexpected member decorated with a RestPartAttribute.");

            return value != null;
        }

        private RestHttpPartInfo m_httpPartInfo;
        internal RestHttpPart(RestHttpPartInfo httpPartInfo) {
            m_httpPartInfo = httpPartInfo;
        }

        internal RestHttpPartInfo HttpPartInfo {
            get { return m_httpPartInfo; }
        }
    }
    internal sealed class RestProtocol : RestHttpPart {
        private bool m_https;
        internal RestProtocol(RestProtocolInfo protocolInfo, object context, object argumentOrValue)
            : base(protocolInfo) {
            m_https = protocolInfo.Https;
        }
        internal bool Https {
            get { return m_https; }
        }
        public override string ToString() {
            return Https ? "https" : "http";
        }
    }
    internal sealed class RestHost : RestHttpPart {
        private RestVariable m_value;
        internal RestHost(RestHostInfo hostInfo, object context, object argumentOrValue)
            : base(hostInfo) {
            m_value = new RestVariable(hostInfo, context, argumentOrValue);
        }
        internal string Value {
            get { return m_value; }
        }
        public override string ToString() {
            return m_value;
        }
    }
    internal sealed class RestPath : RestHttpPart {
        private RestVariable m_value;
        internal RestPath(RestPathInfo pathInfo, object context, object argumentOrValue)
            : base(pathInfo) {
            m_value = new RestVariable(pathInfo, context, argumentOrValue);
        }
        internal string Value {
            get { return m_value; }
        }
        public override string ToString() {
            return m_value;
        }
    }
    internal sealed class RestQuery : RestHttpPart {
        private RestVariable m_value;
        private string m_name;
        internal RestQuery(RestQueryInfo queryInfo, object context, object argumentOrValue)
            : base(queryInfo) {

            m_value = new RestVariable(queryInfo, context, argumentOrValue);
            m_name = queryInfo.Name;
        }

        public string Value {
            get { return m_value; }
        }
        public string Name {
            get { return m_name; }
        }
        public override string ToString() {
            return new TextJoin("=") { Name, Value }.ReadToEnd();
        }
    }
    internal sealed class RestHeader : RestHttpPart {
        private readonly string m_name;
        private RestVariable m_value;

        internal RestHeader(RestHeaderInfo headerInfo, object context, object argumentOrValue)
            : base(headerInfo) {

            m_value = new RestVariable(headerInfo, context, argumentOrValue);
            m_name = headerInfo.Name;
        }

        internal object Object {
            get { return m_value.Object; }
        }
        internal string Value {
            get { return m_value; }
        }
        internal string Name {
            get { return m_name; }
        }
        public override string ToString() {
            return new TextJoin(": ") { Name, Value }.ReadToEnd();
        }
    }
    internal sealed class RestCertificate : RestHttpPart {
        private X509Certificate m_certificate;

        internal RestCertificate(RestCertificateInfo certificateInfo, object context, object argumentOrValue)
            : base(certificateInfo) {
            m_certificate = (X509Certificate)argumentOrValue;
        }

        internal X509Certificate Certificate {
            get { return m_certificate; }
        }
        public override string ToString() {
            return m_certificate.Subject;
        }
    }
    internal sealed class RestContent : RestHttpPart {
        private object m_value;
        private string m_name;

        internal RestContent(RestContentInfo contentInfo, object context, object argumentOrValue)
            : base(contentInfo) {

            m_name = contentInfo.Name;
            m_value = contentInfo.ValueOrFormat ?? argumentOrValue;
        }

        public string Name {
            get { return m_name; }
        }
        public object Value {
            get { return m_value; }
        }

        public override string ToString() {
            return new TextJoin(' ') { Name, Value }.ReadToEnd();
        }
    }

    internal abstract class RestDynamicHttpPart : RestHttpPart, IEnumerable<RestHttpPart> {
        private object m_argumentOrValue;

        internal RestDynamicHttpPart(RestDynamicHttpPartInfo dynamicHttpPart, object argumentOrValue)
            : base(dynamicHttpPart) {

            m_argumentOrValue = argumentOrValue;
        }

        public IEnumerator<RestHttpPart> GetEnumerator() {
            var info = RestLoader.GetType(m_argumentOrValue.GetType());
            var contextPartInfos = info.HttpParts().ToArray();
            var contextParts = contextPartInfos.SelectMany(o => RestHttpPart.Construct(o, m_argumentOrValue));
            return contextParts.GetEnumerator();
        }
        IEnumerator IEnumerable.GetEnumerator() {
            return GetEnumerator();
        }
    }
    internal sealed class RestParent : RestDynamicHttpPart {
        internal RestParent(RestParentInfo parentInfo, object context, object argumentOrValue)
            : base(parentInfo, argumentOrValue) {
        }
    }
    internal sealed class RestNakedParameter : RestDynamicHttpPart {
        internal RestNakedParameter(RestNakedParameterInfo nakedParameterInfo, object context, object argumentOrValue)
            : base(nakedParameterInfo, argumentOrValue) {
        }
    }

    public abstract class RestObject {
        private ConstructorInfo m_constructorInfo;
        private IEnumerable<object> m_arguments;

        public RestObject(params object[] arguments) {
            m_constructorInfo = (ConstructorInfo)
                new StackTrace().Methods().First(o => o.DeclaringType == GetType());
            m_arguments = arguments;
        }

        internal ConstructorInfo Constructor {
            get { return m_constructorInfo; }
        }
    }
    [DebuggerDisplay("Value = {Value}")]
    public sealed class RestArgument : ICustomAttributeProvider {
        private object m_value;
        private string m_name;
        private ICustomAttributeProvider m_cap;

        internal RestArgument(RestContent content) {
            m_value = content.Value;
            m_name = content.Name;
            m_cap = content.HttpPartInfo.Member.AttributeProvider;
        }

        public ICustomAttributeProvider Target {
            get { return m_cap; }
        }
        public object Value {
            get { return m_value; }
        }
        public string Name {
            get { return m_name; }
        }

        public object[] GetCustomAttributes(bool inherit) {
            return m_cap.GetCustomAttributes(inherit);
        }
        public object[] GetCustomAttributes(Type attributeType, bool inherit) {
            return m_cap.GetCustomAttributes(attributeType, inherit);
        }
        public bool IsDefined(Type attributeType, bool inherit) {
            return m_cap.IsDefined(attributeType, inherit);
        }

        public override string ToString() {
            return Value.ToString();
        }
    }
    public static class RestRequest {
        private static Dictionary<string, HttpRequestHeader> s_standardHeaders;
        
        static RestRequest() {
            s_standardHeaders = typeof(HttpRequestHeader).Get<FieldInfo>(isStatic: true)
                .ToCaseInsensitiveDictionary(
                    o => o.Name,
                    o => (HttpRequestHeader)o.GetValue()
                );
        }

        private static Uri GetUri(IEnumerable<RestHttpPart> httpParts) {

            var protocol = "http";
            var protocolPart = httpParts.OfType<RestProtocol>().FirstOrDefault();
            if (protocolPart != null && protocolPart.Https)
                protocol = "https";

            var port = default(int?);

            var hosts = httpParts.OfType<RestHost>();
            var paths = httpParts.OfType<RestPath>();
            var query = httpParts.OfType<RestQuery>();

            var uri = new TextJoin('?', 
                prefix: new TextJoin(suffix: "//" ) { protocol, ':', port }) {

                // path 
                new TextJoin { 

                    // host
                    new TextJoin('.', suffix: '/') { hosts }, 
                    
                    // segments
                    new TextJoin('/') { paths.Reverse() }, 
                },

                // query
                new TextJoin('&') { 
                    query.Select(o => new TextJoin('=') {
                        o.Name, o.Value
                    })
                }

            }.ReadToEnd();

            return new Uri(uri);
        }
        private static X509Certificate GetCertificate(IEnumerable<RestHttpPart> httpParts) {
            var certificatePart = httpParts.OfType<RestCertificate>().FirstOrDefault();
            if (certificatePart == null)
                return null;
            return certificatePart.Certificate;
        }
        private static void AddHeaders(HttpWebRequest request, IEnumerable<RestHeader> restHeaders) {
            var httpHeaders = request.Headers;

            foreach (var restHeader in restHeaders) {
                var name = restHeader.Name;
                var value = restHeader.Value;

                // known headers
                HttpRequestHeader standardHeader;
                if (s_standardHeaders.TryGetValue(restHeader.Name, out standardHeader)) {

                    // standard headers
                    if (standardHeader == HttpRequestHeader.ContentType)
                        request.ContentType = value;
                    
                    else if (standardHeader == HttpRequestHeader.Date) {
                        if (!(restHeader.Object is DateTime))
                            throw new ArgumentException("Date header must be of type DateTime.");
                        request.Date = (DateTime)restHeader.Object;
                    }
                    
                    // other known header
                    else request.Headers.Add(standardHeader, value);
                }

                // custom headers
                else httpHeaders.Add(restHeader.Name, restHeader.Value);
            }
        }

        private static HttpWebRequest Construct(
            Uri url,
            string method,
            string contentType = null,
            IEnumerable<RestHeader> headers = null,
            X509Certificate certificate = null,
            string content = null) {

            // url
            var request = (HttpWebRequest)HttpWebRequest.Create(url);

            // POST, GET, DELETE, etc
            request.Method = method;

            // content type
            request.ContentType = contentType;

            // headers
            if (headers != null)
                AddHeaders(request, headers);

            // certificate
            if (certificate != null)
                request.ClientCertificates.Add(certificate);

            // content
            if (!string.IsNullOrEmpty(content)) {
                var bytes = Encoding.UTF8.GetBytes(content);
                request.ContentLength = bytes.Length;
                using (var streamWriter = request.GetRequestStream())
                    streamWriter.Write(bytes, 0, bytes.Length);
            }

            return request;
        }

        public static HttpWebRequest Construct(
            MethodBase method, 
            object context,
            IEnumerable<object> arguments, 
            out object body) {

            // get the rest metadata associated with the method called
            var request = RestLoader.GetRequest((MethodInfo)method);

            // zip arguments with parameter metadata
            var parameterHttpPartInfos = request.ParameterHttpParts().ToArray();
            var argumentParts = parameterHttpPartInfos.Zip(arguments, 
                (parameter, argument) => RestHttpPart.Construct(parameter, context, argument))
                .SelectMany().ToArray();

            // join context with metadata
            var contextHttpPartInfos = request.ContextHttpParts().ToArray();
            var contextHttpParts = contextHttpPartInfos.SelectMany(o => RestHttpPart.Construct(o, context)).ToArray();

            // concat parts
            var httpParts = argumentParts.Concat(contextHttpParts).WhereNotDefault().ToArray();

            // get custom arguments to be used by caller to construct body
            body = null;
            var templateArguments = httpParts.OfType<RestContent>().Select(o => new RestArgument(o)).ToArray();
            if (templateArguments.Any()) {

                // activate template
                var templateInfo = request.Template;
                if (templateInfo == null)
                    throw new ArgumentException("No type attribute with RestTemplateAttribute found.");

                var templateType = templateInfo.Type;
                var template = templateType.Activate(new[] { templateArguments });

                // gather template http parts
                var restTemplateType = RestLoader.GetType(templateType);
                var templateHttpParts = restTemplateType.HttpParts()
                    .SelectMany(o => RestHttpPart.Construct(o, template)).ToArray();

                // return template and its parts
                httpParts = httpParts.Concat(templateHttpParts).ToArray();
                body = template;
            }

            // extract http elements from parts
            var uri = GetUri(httpParts);
            var headers = httpParts.OfType<RestHeader>();
            var certificate = GetCertificate(httpParts);

            // construct request
            var httpRequest = RestRequest.Construct(
                uri, request.HttpMethod,
                certificate: certificate,
                headers: headers
            );

            return httpRequest;
        }
    }

    public static class RestResponse {

        public static IEnumerable<T> Deserialize<T>(
            HttpWebResponse response,
            XName root = null) {

            return null;
        }
    }
}
